#include "config.h"
#include "Connection.h"

Connection::Connection( struct sockaddr_in src_addr )
{
	// reset socket
	m_incoming_sock = -1;
	m_outgoing_sock = -1;
	
	// allocate bufs
	m_incoming_recv_buf = new char[CONNECTION_RECV_BUF_MAX];
	m_outgoing_recv_buf = new char[CONNECTION_RECV_BUF_MAX];
	
	// reset buf len
	m_incoming_recv_buf_len = m_outgoing_recv_buf_len = 0;
	
	// reset address
	memset( &m_original_outgoing_destination, 0, sizeof(struct sockaddr_in) );
	
	// store src addr
	m_src_addr = src_addr;
}

Connection::~Connection()
{
	// close sockets
	if( m_incoming_sock != -1 ) close( m_incoming_sock );
	if( m_outgoing_sock != -1 ) close( m_outgoing_sock );
	
	// free bufs
	delete [] m_incoming_recv_buf;
	delete [] m_outgoing_recv_buf;
	
	// removed
	DEBUG_OUTPUT_WITH_FUNCTION_NAME( DEBUG_LEVEL_NOTICE, "remove client: %s:%d",
		inet_ntoa(m_src_addr.sin_addr), ntohs(m_src_addr.sin_port) );
}

int Connection::Init( const int incoming_sock )
{
	// store incoming_sock
	m_incoming_sock = incoming_sock;
	
	// get original socket destination
	socklen_t original_outgoing_destination_size = sizeof(struct sockaddr_in);
	if( getsockopt(m_incoming_sock, SOL_IP, SO_ORIGINAL_DST, &m_original_outgoing_destination, &original_outgoing_destination_size) )
	{
		DEBUG_OUTPUT_WITH_FUNCTION_NAME( DEBUG_LEVEL_ERROR, "get original destination failed." );
		return -1;
	}
	
	// create outgoing_sock
	return DoConnect();
}

int Connection::SetReadFds( fd_set* p_read_fds, int* p_max_read_fd )
{
	FD_SET( m_incoming_sock, p_read_fds );
	if( m_incoming_sock > *p_max_read_fd ) *p_max_read_fd = m_incoming_sock;
	FD_SET( m_outgoing_sock, p_read_fds );
	if( m_outgoing_sock > *p_max_read_fd ) *p_max_read_fd = m_outgoing_sock;
	
	return 0;
}

int Connection::Process( fd_set* p_read_fds )
{
	DEBUG_OUTPUT_WITH_FUNCTION_NAME( DEBUG_LEVEL_ERROR, "p_read_fds: %08x", p_read_fds );
	
	// check if need to read incoming
	if( m_incoming_recv_buf_len < CONNECTION_RECV_BUF_MAX && FD_ISSET(m_incoming_sock, p_read_fds) )
	{
		if( Recv(m_incoming_sock, m_incoming_recv_buf, &m_incoming_recv_buf_len) != 0 )
		{
			DEBUG_OUTPUT_WITH_FUNCTION_NAME( DEBUG_LEVEL_ERROR, "recv incoming failed." );
			return -1;
		}
	}
	
	// check if need to read outgoing
	if( m_outgoing_recv_buf_len < CONNECTION_RECV_BUF_MAX && FD_ISSET(m_outgoing_sock, p_read_fds) )
	{
		if( Recv(m_outgoing_sock, m_outgoing_recv_buf, &m_outgoing_recv_buf_len) != 0 )
		{
			DEBUG_OUTPUT_WITH_FUNCTION_NAME( DEBUG_LEVEL_ERROR, "recv outgoing failed." );
			return -2;
		}
	}
	
	// check if need to send incoming
	if( m_outgoing_recv_buf_len > 0 )
	{
		if( Send(m_incoming_sock, m_outgoing_recv_buf, &m_outgoing_recv_buf_len) != 0 )
		{
			DEBUG_OUTPUT_WITH_FUNCTION_NAME( DEBUG_LEVEL_ERROR, "send incoming failed." );
			return -3;
		}
	}
		
	// check if need to send outgoing
	if( m_incoming_recv_buf_len > 0 )
	{
		if( Send(m_outgoing_sock, m_incoming_recv_buf, &m_incoming_recv_buf_len) != 0 )
		{
			DEBUG_OUTPUT_WITH_FUNCTION_NAME( DEBUG_LEVEL_ERROR, "send outgoing failed." );
			return -4;
		}
	}
	
	return 0;
}

int Connection::DoConnect()
{
	// create 
	m_outgoing_sock = socket( AF_INET, SOCK_STREAM, IPPROTO_IP );
	if( m_outgoing_sock == -1 )
	{
		DEBUG_OUTPUT_WITH_FUNCTION_NAME( DEBUG_LEVEL_ERROR, "create outgoing socket failed." );
		return -1;
	}

	// connect to server
	if( connect(m_outgoing_sock, (struct sockaddr*)&m_original_outgoing_destination, sizeof(m_original_outgoing_destination)) )
	{
		DEBUG_OUTPUT_WITH_FUNCTION_NAME( DEBUG_LEVEL_ERROR, "connect to server failed." );
		return -2;
	}
	
	return 0;
}

int Connection::Send( const int sock, char* buf, int* p_data_len )
{
	int bytes_sent = DoSend( sock, buf, *p_data_len );
	
	DEBUG_OUTPUT_WITH_FUNCTION_NAME( DEBUG_LEVEL_DEBUG, "bytes_sent: %d, data: %s", bytes_sent, buf );

	// sent some data
	if( bytes_sent > 0 )
	{
		*p_data_len -= bytes_sent;
		memmove( buf, buf+bytes_sent, *p_data_len );
	}
	// sent failed
	else
	{
		// errno 11, 35 (not caused by non-blocking)
		if ( errno != EAGAIN && errno != EWOULDBLOCK ) return -1;
	}
	
	return 0;
}

int Connection::Recv( const int sock, char* buf, int* p_data_len )
{
	// get buf available
	int buf_len_available = CONNECTION_RECV_BUF_MAX - *p_data_len;
	
	// do recv
	int bytes_received = DoRecv( sock, buf+*p_data_len, buf_len_available );
	DEBUG_OUTPUT_WITH_FUNCTION_NAME( DEBUG_LEVEL_DEBUG, "bytes_received: %d, data: %s", bytes_received, buf+*p_data_len );
	if( bytes_received > 0 ) *p_data_len += bytes_received;
	else
	{
		// errno 11, 35 (not caused by non-blocking)
		if ( errno != EAGAIN && errno != EWOULDBLOCK ) return -1;
	}

	return 0;
}
	
int Connection::DoSend( const int sock, const char* data, const int len )
{
	return send( sock, data, len, 0 );
}

int Connection::DoRecv( const int sock, char* buf, const int len )
{
	return recv( sock, buf, len, 0 );
}
